let ZhuojiGameConstant = require('../constant/zhuojiGameConstant');
let logger = require("pomelo-logger").getLogger(__filename);


class MahjongUtils {

    static checkHu(cards) {
        return this.check7Pair(cards) || this.check33332(cards);
    }

    static checkTing13(cards) {
        let targetCards = new Set();
        if (cards.length !== 1 && cards.length !== 4 &&
            cards.length !== 7 && cards.length !== 10 && cards.length !== 13) {
            logger.error("checkTing13 error. cards=" + cards);
            return targetCards;
        }
        let allCards = this.getAllCards();
        for (let card of allCards) {
            cards.push(card);
            if (this.checkHu(cards)) {
                targetCards.add(card);
            }
            cards.pop();
        }
        return targetCards;
    }

    static checkTing14(cards) {
        let candidates = new Map();
        if (cards.length !== 2 && cards.length !== 5 &&
            cards.length !== 8 && cards.length !== 11 && cards.length !== 14) {
            logger.error("checkTing14 error. cards=" + cards);
            return candidates;
        }
        let cardCounts = this.calculateCardCount(cards);
        for (let [removedCard, counts] of cardCounts.entries()) {
            let newCards = [];
            for(let card of cards) {
                if(card !== removedCard) {
                    newCards.push(card);
                }
            }
            for(let i = 0; i < counts - 1; i ++) {
                newCards.push(removedCard);
            }
            let targets = this.checkTing13(newCards);
            if (targets.size > 0) {
                candidates.set(removedCard, targets);
            }
        }
        return candidates;
    }

    static isTing(cards) {
        if (cards.length !== 1 && cards.length !== 4 &&
            cards.length !== 7 && cards.length !== 10 &&
            cards.length !== 13) {
            logger.error("isTing error. cards=" + cards);
            return false;
        }
        let allCards = this.getAllCards();
        for (let card of allCards) {
            cards.push(card);
            if (this.checkHu(cards)) {
                return true;
            }
            cards.pop();
        }
        return false;
    }

    static check7Pair(cards) {
        if(cards.length !== 14) {
            return false;
        }
        let cardCounts = this.calculateCardCount(cards);
        for(let counts of cardCounts.values()) {
            if(counts % 2 !== 0){
                return false;
            }
        }
        return true;
    }

    static check33332(cards) {
        let wan = this.calculateValueByColor(cards, 0x00);
        let tong = this.calculateValueByColor(cards, 0x10);
        let tiao = this.calculateValueByColor(cards, 0x20);
        return this.check(tiao, tong, wan);
    }

    static calculateCardCount(cards) {
        let cardCounts = new Map();
        for (let card of cards) {
            if (cardCounts.has(card)) {
                cardCounts.set(card, cardCounts.get(card) + 1);
            } else {
                cardCounts.set(card, 1);
            }
        }
        return cardCounts;
    }

    static calculateValueByColor(cards, colorMask) {
        let s = [0, 0, 0, 0, 0, 0, 0, 0, 0];
        for (let card of cards) {
            let color = card & 0x70;
            let value = (card & 0x0f) - 1;
            if (color === colorMask) {
                s[value] = s[value] + 1;
            }
        }
        return this.encode(s);
    }

    static encode(v) {
        if (v.length !== 9) {
            return 0;
        }
        let value = 0;
        for (let i = 0; i < 9; i++) {
            value |= (v[i] << (i * 3));
        }
        return value;
    }

    static check(tiao, tong, wan) {
        let totalTiao = this.count(tiao);
        let totalTong = this.count(tong);
        let totalWan = this.count(wan);
        if (totalTiao % 3 === 0 && totalTong % 3 === 0 && totalWan % 3 === 2 &&
            this.check3(tiao) && this.check3(tong) && this.check32(wan)) {
            return true;
        }
        if (totalTiao % 3 === 0 && totalTong % 3 === 2 && totalWan % 3 === 0 &&
            this.check3(tiao) && this.check32(tong) && this.check3(wan)) {
            return true;
        }
        if (totalTiao % 3 === 2 && totalTong % 3 === 0 && totalWan % 3 === 0 &&
            this.check32(tiao) && this.check3(tong) && this.check3(wan)) {
            return true;
        }
        return false;
    }

    static check3(value) {
        if (value === 0) {
            return true;
        }
        let total = this.count(value);
        if (total % 3 !== 0) {
            return false;
        }
        for (let i = 1; i <= 9; i++) {
            let count = this.get(value, i);
            if (count >= 3 && this.check3(this.set(value, i, count - 3))) {
                return true;
            }
            if (i <= 7) {
                let v = value;
                let count1 = this.get(v, i);
                let count2 = this.get(v, i + 1);
                let count3 = this.get(v, i + 2);
                if (count1 >= 1 && count2 >= 1 && count3 >= 1) {
                    v = this.set(v, i, count1 - 1);
                    v = this.set(v, i + 1, count2 - 1);
                    v = this.set(v, i + 2, count3 - 1);
                    if (this.check3(v)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    static check32(value) {
        let total = this.count(value);
        if (total % 3 !== 2) {
            return false;
        }
        for (let i = 1; i <= 9; i++) {
            let c = this.get(value, i);
            if (c >= 2 && this.check3(this.set(value, i, c - 2))) {
                return true;
            }
        }
        return false;
    }

    static count(value) {
        let total = 0;
        for (let i = 1; i <= 9; i++) {
            total += this.get(value, i);
        }
        return total;
    }
    static get(value, index) {
        let shiftCount = (index - 1) * 3;
        let count = (value & (0x07 << shiftCount)) >> shiftCount;
        return count;
    }
    static set(value, index, count) {
        let shiftCount = (index - 1) * 3;
        let v1 = count << shiftCount;
        let mask = 0x07 << shiftCount;
        mask = ~mask;
        return (mask & value) | v1;
    }

    static getAllCards() {
        let all = new Set();
        for (let j = 1; j <= 9; j++) {
            all.add(0x00 | j);
            all.add(0x10 | j);
            all.add(0x20 | j);
        }
        return all;
    }

    static calculateHuType(cardsInHand, cardsDown, targetCard) {
        let allCards = [...cardsInHand];
        if(targetCard !== 0) {
            allCards.push(targetCard);
        }
        let isSevenPair = this.check7Pair(allCards);
        let is33332 = this.check33332(allCards);
        let has4 = this.hasFour(allCards);
        let isSameSuite = this.checkSameSuit(allCards, cardsDown);
        let isPengPeng = this.checkAllPeng(allCards, cardsDown);
        if (isSevenPair && isSameSuite && has4) {
            return ZhuojiGameConstant.HU_TYPE_QING_LONG_BEI;
        }
        if (isSevenPair && isSameSuite && (!has4)) {
            return ZhuojiGameConstant.HU_TYPE_QING_7_DUI;
        }
        if (isSevenPair && (!isSameSuite) && has4) {
            return ZhuojiGameConstant.HU_TYPE_LONG_7_DUI;
        }
        if (isSevenPair && (!isSameSuite) && (!has4)) {
            return ZhuojiGameConstant.HU_TYPE_7_DUI;
        }
        if (is33332 && isSameSuite && allCards.length === 2) {
            return ZhuojiGameConstant.HU_TYPE_QING_DAN_DIAO;
        }
        if (is33332 && (!isSameSuite) && allCards.length === 2) {
            return ZhuojiGameConstant.HU_TYPE_DAN_DIAO;
        }
        if (is33332 && isSameSuite && allCards.length > 2 && isPengPeng) {
            return ZhuojiGameConstant.HU_TYPE_QING_DA_DUI;
        }
        if (is33332 && isPengPeng && (!isSameSuite)) {
            return ZhuojiGameConstant.HU_TYPE_DA_DUI_ZI;
        }
        if (is33332 && (!isPengPeng) && isSameSuite) {
            return ZhuojiGameConstant.HU_TYPE_QING_YI_SE;
        }
        if (is33332 && (!isPengPeng) && (!isSameSuite)) {
            return ZhuojiGameConstant.HU_TYPE_PING_HU;
        }
        return 0;
    }

    static hasFour(cards) {
        let stat = new Map();
        for (let card of cards) {
            if (!stat.has(card)) {
                stat.set(card, 1);
            } else {
                stat.set(card, stat.get(card) + 1);
            }
        }
        for (let card of stat.keys()) {
            if (stat.get(card) === 4) {
                return true;
            }
        }
        return false;
    }

    static checkSameSuit(cardsInHand, cardsDown) {
        let colorStat = new Map();
        for (let card of cardsInHand) {
            let color = card & 0x70;
            if (colorStat.has(color)) {
                colorStat.set(color, colorStat.get(color) + 1);
            } else {
                colorStat.set(color, 1);
            }
        }
        for (let cardDown of cardsDown) {
            let value = cardDown.cards[0];
            let color = value & 0x70;
            if (colorStat.has(color)) {
                colorStat.set(color, colorStat.get(color) + 1);
            } else {
                colorStat.set(color, 1);
            }
        }
        return colorStat.size === 1;
    }

    static checkAllPeng(cardsInHand, cardDowns) {
        let stat = new Map();
        for (let card of cardsInHand) {
            if (!stat.has(card)) {
                stat.set(card, 1);
            } else {
                stat.set(card, stat.get(card) + 1);
            }
        }
        let num1Count = 0;
        let num2Count = 0;
        let num3Count = 0;
        let num4Count = 0;
        for (let [card, num] of stat.entries()) {
            if (num === 1) {
                num1Count += 1;
            } else if (num === 2) {
                num2Count += 1;
            } else if (num === 3) {
                num3Count += 1;
            } else if (num === 4) {
                num4Count += 1;
            }
        }
        return num1Count === 0 && num2Count === 1 && num3Count >= 0 && num4Count === 0;
    }

    static calculateTingFan(cardsInHand, cardsDown, playType) {
        let cards = this.checkTing13(cardsInHand);
        if (cards.size === 0) {
            return 0;
        }
        let huFan = 0;
        for (let card of cards) {
            let tmpHuType = this.calculateHuType(cardsInHand, cardsDown, card);
            let tmpHuFan = this.getHuTypeFan(tmpHuType, playType);
            if (tmpHuFan > huFan) {
                huFan = tmpHuFan;
            }
        }
        return huFan;
    }

    static getHuTypeFan(type, playType) {
        let typeToFan = new Map();
        if(playType === ZhuojiGameConstant.PLAY_TYPE_YANG_YANG_SAN){
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_PING_HU, 3);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_DA_DUI_ZI, 8+3);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_DAN_DIAO, 13+3);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_7_DUI, 13+3);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_QING_YI_SE, 13+3);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_LONG_7_DUI, 26+3);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_QING_DA_DUI, 26+3);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_QING_DAN_DIAO, 26+3);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_QING_7_DUI, 26+3);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_QING_LONG_BEI, 39+3);
        } else {
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_PING_HU, 3);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_DA_DUI_ZI, 8);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_DAN_DIAO, 13);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_7_DUI, 13);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_QING_YI_SE, 13);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_LONG_7_DUI, 26);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_QING_DA_DUI, 26);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_QING_DAN_DIAO, 26);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_QING_7_DUI, 26);
            typeToFan.set(ZhuojiGameConstant.HU_TYPE_QING_LONG_BEI, 39);
        }

        return typeToFan.has(type) ? typeToFan.get(type) : 0;
    }

    static getHuTypeName(type) {
        let typeToName = new Map();
        typeToName.set(ZhuojiGameConstant.HU_TYPE_PING_HU, "平胡");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_DA_DUI_ZI, "大对子");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_7_DUI, "七对");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_LONG_7_DUI, "龙七对");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_QING_YI_SE, "清一色");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_QING_DA_DUI, "清大对");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_QING_DAN_DIAO, "清单吊");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_QING_7_DUI, "清七对");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_QING_LONG_BEI, "清龙背");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_DAN_DIAO, "单吊");
        return typeToName.has(type) ? typeToName.get(type) : "";
    }

    static getHuTypeEnName(type) {
        let typeToName = new Map();
        typeToName.set(ZhuojiGameConstant.HU_TYPE_PING_HU, "pinghu");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_DA_DUI_ZI, "daduizi");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_7_DUI, "qidui");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_LONG_7_DUI, "longqidui");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_QING_YI_SE, "qingyise");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_QING_DA_DUI, "qingdadui");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_QING_DAN_DIAO, "qingdandiao");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_QING_7_DUI, "qingqidui");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_QING_LONG_BEI, "qinglongbei");
        typeToName.set(ZhuojiGameConstant.HU_TYPE_DAN_DIAO, "dandiao");
        return typeToName.has(type) ? typeToName.get(type) : "";
    }

    static getHuExtraTypeFan(type) {
        let huExtraTypeToFan = new Map();
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_TING_YING_BAO, 26);
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_TING_RUAN_BAO, 13);
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_SHA_BAO, 13);
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_MING_GANG_MO, 2);
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_BU_GANG_MO, 2);     //need ensure
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_AN_GANG_MO, 3);
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_MING_GANG_PAO, 2);  //need ensure
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_BU_GANG_PAO, 2);    //need ensure
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_AN_GANG_PAO, 3);    //need ensure
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_QIANG_GANG_HU, 3);
        return huExtraTypeToFan.has(type) ? huExtraTypeToFan.get(type) : 0;
    }

    static getHuExtraTypeName(type) {
        let huExtraTypeToFan = new Map();
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_TING_YING_BAO, "硬报");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_TING_RUAN_BAO, "软报");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_SHA_BAO, "杀报");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_MING_GANG_MO, "杠上开花");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_AN_GANG_MO, "杠上开花");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_BU_GANG_MO, "杠上开花");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_MING_GANG_PAO, "热炮");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_AN_GANG_PAO, "热炮");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_BU_GANG_PAO, "热炮");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_QIANG_GANG_HU, "抢杠胡");
        return huExtraTypeToFan.has(type) ? huExtraTypeToFan.get(type) : "";
    }

    static getHuExtraTypeEnName(type) {
        let huExtraTypeToFan = new Map();
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_TING_YING_BAO, "yingbao");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_TING_RUAN_BAO, "ruanbao");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_SHA_BAO, "shabao");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_MING_GANG_MO, "gangshangkaihua");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_AN_GANG_MO, "gangshangkaihua");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_BU_GANG_MO, "gangshangkaihua");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_MING_GANG_PAO, "repao");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_AN_GANG_PAO, "repao");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_BU_GANG_PAO, "repao");
        huExtraTypeToFan.set(ZhuojiGameConstant.MAHJONG_HU_CODE_QIANG_GANG_HU, "qiangganghu");
        return huExtraTypeToFan.has(type) ? huExtraTypeToFan.get(type) : "";
    }
}

module.exports = MahjongUtils;